/*
 * lab3chq21.c
 *
 *  Created on: Mar 6th, 2011
 *      Author: Vishal Verma
 */

/*
 * PROGRAM DESCRIPTION
 *
 * Run I2C bit banged.
 *
 */

/*
 * COMPILER FLAGS
 * sdcc -c --debug -mmcs51 --std-sdcc99 --model-large
 *
 * LINKER FLAGS
 * sdcc --debug -mmcs51 --code-loc 0x0000 --code-size 0x8000 --xram-loc 0x0000 --xram-size 0x8000 --model-large --out-fmt-ihx
 *
 * USAGE SPECIFICATIONS:
 * Baud rate - 57600
 */

/*==============================================INCLUDES=========================================*/
#include <at89c51ed2.h>  //also includes 8052.h and 8051.h
#include <mcs51reg.h>
#include <stdio.h>
#include <stdlib.h>
#include "i2c.h"
#include "lcd.h"
#include "delay.h"

/*==============================================DEFINES==========================================*/
#define TRUE 1
#define FALSE 0
#define ON 1
#define OFF 0
#define IOXREAD 0x41
#define IOXWRITE 0x40
#define IOXSIZE 8
#define IOXINPUT 1
#define IOXOUTPUT 0

/*==============================================GLOBALS==========================================*/

unsigned int f_intRecvd = FALSE;
unsigned char g_ioxData = 0;
enum RETCODE{							//retcodes/parameter flags for all functions
	OK = 0,
	STDERR = 5151,						//bad idea to keep 5151 but using coz needed to return an unsigned int in one place
	EINMEM = -7
};

/*=========================================FUNCTION PROTOTYPES===================================*/

void putchar (char c);
char getchar (void);
int putstr (char *s);
int getstr (char *s);
void serialInit(int);
void clearScr(void);
void myDelay(int j);
void initAll(void);

void printMenu(void);
void menuInvalid(void);
int getInput(char *prompt, char *retStr, int sz_retStr, int f_clear, int f_noWait);
int hexalpha_to_int(int c);
unsigned int htoi(const char s[]);

/*=========================================FUNCTION DEFINITIONS===================================*/

/*
* NAME :            _sdcc_external_startup
*
* DESCRIPTION :     Will run before main(). Used to initialize registers etc. as required
*
* INPUTS :
*       PARAMETERS:	-
*		GLOBALS:	AUXR
*
* OUTPUTS :
*       GLOBALS:	AUXR
*       RETURN:		-
*
*/

_sdcc_external_startup() {

	AUXR |= 0x0C;
	AUXR &= 0x7F;
	return 0;
}

/*
* NAME :            putchar
*
* DESCRIPTION :     print 1 character to serial port
*
* INPUTS :
*       PARAMETERS:	char c
*		GLOBALS:	SCON
*
* OUTPUTS :
*       GLOBALS :	SBUF, TI
*       RETURN :	-
*
* PROCESS :
*                   [1]	Wait for port to be ready
* 					[2]	Print char and clear TI
*/

void putchar (char c) {
	SBUF = c;  	                // load serial port with transmit value
	while (!TI);	            // check T1 flag
	TI = 0;  	                // clear TI flag
}

/*
* NAME :            getchar
*
* DESCRIPTION :     get 1 character from serial port
*
* INPUTS :
*       PARAMETERS:	-
*		GLOBALS:	SCON
*
* OUTPUTS :
*       GLOBALS :	SBUF, RI
*       RETURN :	SBUF - character received
*
* PROCESS :
*                   [1]	Wait for character to be received
* 					[2]	Clear RI
*					[3] Return character
*/

char getchar () {
    while (!RI);                // compare asm code generated for these three lines
	RI = 0;			            // clear RI flag
	return SBUF;  	            // return character from SBUF
}

/*
* NAME :            putstr
*
* DESCRIPTION :     Print a string to serial port
*
* INPUTS :
*       PARAMETERS:	char *s
*		GLOBALS:	-
*
* OUTPUTS :
*       GLOBALS :	-
*       RETURN :	Number of characters transmitted
*
* PROCESS :
*                   [1]  Transmit characters till oxoo (NULL) is encountered
*/

int putstr (char *s) {

	int i = 0;
	while (*s){									// output characters until NULL found
		putchar(*s++);
		i++;
	}
	//putchar('\n\r');
	return i+1;
}

/*
* NAME :            getstr
*
* DESCRIPTION :     Get a string from serial port
*
* INPUTS :
*       PARAMETERS:	char *s
*		GLOBALS:	-
*
* OUTPUTS :
*		PARAMETERS: char *s
*       GLOBALS :	-
*       RETURN :	i - number of characters read
*
* PROCESS :
*                   [1]  Accept characters till 0x0D encountered (CR)
*					[2]	 Store starting at given location (*s)
*
* NOTES :           Is not safe from buffer overrun. Assumes valid memory available for all characters input.
*					Can be substituted with gets() which is equally unsafe but I wrote this so am going with it :)
*/

int getstr (char *s) {

	int i = 0;
	do {
		s[i] = getchar();
		putchar(s[i]);
		i++;
	} while(s[i-1] != 0x0D);  					//0x0D is <CR>
	s[i-1] = 0x00;								//replace <CR> with NULL (0x00)
	putchar('\n\r');
	return (i-1);
}

/*
* NAME :            serialInit
*
* DESCRIPTION :     Initialize the serial port
*
* INPUTS :
*       PARAMETERS:	int br (BAUD RATE)
*		GLOBALS:	-
*
* OUTPUTS :
*       GLOBALS :	PCON, SCON, TMOD, TH1, TR1, TI
*       RETURN :	-
*
*
* NOTES :  Baud rate is supplied as 0xFF for 56700 etc
*/

void serialInit(int br) {

/*
	PCON |= 0x80;								//SMOD1 = 1
	SCON = 0x50;								//uart in mode 1 (8 bit), REN=1
	BDRCON &=0xEC;								//BRR=0; SRC=0;
	BDRCON |=0x0E;								//TBCK=1;RBCK=1; SPD=1
	BRL = br;
	BRL=0xFD;									//115200 Bds at 11.059MHz
	EA = 1;										//Enable serial interrupt
	BDRCON |= 0x10;								//Baud rate generator run
*/

/*

	PCON |= 0x80;								//configure uart, fast baud
	SCON = 0x50;                   			 	//SCON: mode 1, 8-bit UART, enable rcvr
	TMOD |= 0x20;                   			//TMOD: timer 1, mode 2, 8-bit reload for serial
	TMOD |= 0x01;                   			//TMOD: timer 1, mode 2, 8-bit reload for debounce
	TH1 = br;                   				//TH1:  reload value for baudrate
	TR1   = 1;                      			//TR1:  timer 1 run
	TI    = 1;                      			//TI:   set TI to send first char of UART
*/

	br=0;
	EA = 0;
	SCON = 0x50;					  			/* uart in mode 1 (8 bit), REN=1 */
	T2CON &= 0xDC;               				/* EXEN2=0; TR2=0; C/T2#=0; CP/RL2#=0; */
	T2CON |= 0x30;               				/* RCLK = 1; TCLK=1; */
	RCAP2H = 0xFF;                 				/* reload value, 115200 Bds at 11.059MHz */
	RCAP2L = 0xFD;
	TI = 0;
	RI = 0;
	ES = 0;  				   					//for serial transmission
	ET2 = 0;
	TR2 = 1;  									//Timer 2 start
	EA = 1;

}

/*
* NAME :            initAll
*
* DESCRIPTION :     Reset global variables to initial values
*
* INPUTS :
*       PARAMETERS:	-
*		GLOBALS:	-
*
* OUTPUTS :
*       GLOBALS :	-
*       RETURN :	-
*/

void initAll() {

	int i = 0;

	EA = 1;
}

/*
* NAME :            clearScr
*
* DESCRIPTION :     Clears and resets the VT100 terminal
*
* INPUTS :
*       PARAMETERS:	-
*		GLOBALS:	-
*
* OUTPUTS :
*       GLOBALS :	-
*       RETURN :	-
*
*
* NOTES :           Will clear the VT100 terminal by sending control sequences to reset and clear entire screen
*					Reset: ESC c
*					Clear Screen: ESC [ 2 J
*/

void clearScr() {
	printf("\033c");			//reset terminal
	printf("\033[2J");			//clear screen
}

/*
* NAME :            myDelay
*
* DESCRIPTION :     Provide a delay
*
* INPUTS :
*       PARAMETERS:	int j - empirical - j=1 ~ 1 second
*		GLOBALS:	-
*
* OUTPUTS :
*       GLOBALS :	-
*       RETURN :	-
*/

void myDelay(int j) {
	int i = 0;
	int cnt = 0;

	j*=100;
	while(j) {
		for(i=0; i<100; i++);
		for(i=0; i<100; i++);
		for(i=0; i<100; i++);
		for(i=0; i<100; i++);
		for(i=0; i<100; i++);
		for(i=0; i<100; i++);
		for(i=0; i<100; i++);
		for(i=0; i<100; i++);
		for(i=0; i<100; i++);
		for(i=0; i<100; i++);
		j--;
	}
}

/*
* NAME :            hexalpha_to_int
*
* DESCRIPTION :     hex to int helper function
*
* INPUTS :
*       PARAMETERS:	c
*		GLOBALS:	
*
* OUTPUTS :
*		PARAMETERS: answer
*       GLOBALS :	
*       RETURN :	
*
* PROCESS :
*                   [1]  use a lookup table to convert lettered character to integer
*
* NOTES :           This function is taken from http://users.powernet.co.uk/eton/kandr2/krx203.html
*/

int hexalpha_to_int(int c)
{
  char hexalpha[] = "aAbBcCdDeEfF";
  int i;
  int answer = 0;

  for(i = 0; answer == 0 && hexalpha[i] != '\0'; i++)
  {
    if(hexalpha[i] == c)
    {
      answer = 10 + (i / 2);
    }
  }

  return answer;
}

/*
* NAME :            htoi
*
* DESCRIPTION :     convert hex string to integer
*
* INPUTS :
*       PARAMETERS:	s
*		GLOBALS:	
*
* OUTPUTS :
*		PARAMETERS: answer
*       GLOBALS :	
*       RETURN :	
*
* PROCESS :
*                   [1]  get each character and convert to int and accumulate with appropriate weight
*
* NOTES :           This function is taken from http://users.powernet.co.uk/eton/kandr2/krx203.html
*/

unsigned int htoi(const char s[])
{
  unsigned int answer = 0;
  int i = 0;
  int valid = 1;
  int hexit;

  if(s[i] == '0')
  {
    ++i;
    if(s[i] == 'x' || s[i] == 'X')
    {
      ++i;
    }
  }

  while(valid && s[i] != '\0')
  {
    answer = answer * 16;
    if(s[i] >= '0' && s[i] <= '9')
    {
      answer = answer + (s[i] - '0');
    }
    else
    {
      hexit = hexalpha_to_int(s[i]);
      if(hexit == 0)
      {
        valid = 0;
      }
      else
      {
        answer = answer + hexit;
      }
    }

    ++i;
  }

  if(!valid)
  {
    answer = STDERR;
  }

  return answer;
}

/*
* NAME :            menuInvalid
*
* DESCRIPTION :     present op on invalid input
*
* INPUTS :
*       PARAMETERS:	-
*		GLOBALS:	-
*
* OUTPUTS :
*		PARAMETERS: -
*       GLOBALS :	-
*       RETURN :	-
*
* PROCESS :
*                   [1]  display string and return
*
* NOTES :           
*/

void menuInvalid(void) {
	clearScr();
	putstr("Invalid Entry, Try again! \n\r");
	myDelay(1);
}

/*
* NAME :            printMenu
*
* DESCRIPTION :     Print menu structure
*
* INPUTS :
*       PARAMETERS:	-
*		GLOBALS:	-
*
* OUTPUTS :
*		PARAMETERS: -
*       GLOBALS :	-
*       RETURN :	-
*
* PROCESS :
*                   [1]  print menu structure
*
* NOTES :           -
*/

void printMenu() {
	clearScr();
	putstr("************************************WELCOME***********************************\n\r");
	putstr("*                                                                            *\n\r");
	putstr("*  This program will allow the user to use the EEPROM and control the clock  *\n\r");
	putstr("*                                                                            *\n\r");
	putstr("******************************************************************************\n\r\n\r");

	putstr("1-> Set a pin as Input \n\r");
	putstr("2-> Read Mode: Wait for a pin to change\n\r");
	putstr("3-> Set a pin as Output \n\r");
	putstr("4-> Change  state of an output pin \n\r");
	putstr("5-> Read all pins immediately (Forced Read/Poll) \n\r\n\r");

	putstr("******************************************************************************\n\r\n\r");

	putstr("Command > ");
}

/*
* NAME :            getInput
*
* DESCRIPTION :     generic user input function
*
* INPUTS :
*       PARAMETERS:	prompt, sz_retStr, f_clear, f_noWait
*		GLOBALS:	
*
* OUTPUTS :
*		PARAMETERS: retStr
*       GLOBALS :	
*       RETURN :	conv
*
* PROCESS :
*                   [1]  get input from user - options to wait for 'return', clear screen. returns string in retStr and atoi in return value
*
* NOTES :           
*/

int getInput(char *prompt, char *retStr, int sz_retStr, int f_clear, int f_noWait) {			/* if only C allowed parameters to have default values! sigh */

	int i = 0;
	char ch = 0;
	int conv = 0;
	int sz_rc = 0;
	char tmp[2] = {0};

	if(TRUE == f_clear) {
		clearScr();
	}

	putstr(prompt);

	if(TRUE == f_noWait) {
		tmp[0] = getchar();
		conv = atoi(tmp);
		if (sz_retStr == 1) {
			*retStr = tmp[0];
		}
		else if (sz_retStr > 1) {
			retStr[0] = tmp[0];
			retStr[1] = tmp[1];
		}
	}
	else {
		sz_rc = getstr(retStr);
		if (sz_rc > sz_retStr) return EINMEM;
		conv = atoi(retStr);
	}

	return conv;
}

char writeIox(char ioxdata) {
	start();
	if(write(IOXWRITE) == ACK) {
		if(write(ioxdata) == ACK) {
			stop();
			printf("Data Written \n\r");
		}
		else {
			printf("Error writing data\n\r");
			return ERROR;
		}
	}
	else {
		printf("Control byte failed\n\r");
		return ERROR;
	}
	return SUCCESS;
}


void ioxPrintIo(unsigned char ioxst[]) {
	//printf("%d", myarr[0]);
	char idx=0;
	printf("\n\r");
	putstr("Pins I/O status:\n\r\n\r");
	printf("Pin: 7 6 5 4 3 2 1 0\n\r");
	printf("--------------------\n\r");
	printf("     ");
	for(idx=7;idx>=0;idx--) {
		ioxst[idx]==1? printf("I "):printf("O ");
	}
	printf("\n\r");
}


void ioxPrintData(unsigned char ioxdata) {

	char idx=0;
	unsigned char ioxbit =0;

	printf("\n\rPins Logic levels:\n\r\n\r");
	printf("Pin: 7 6 5 4 3 2 1 0\n\r");
	printf("--------------------\n\r");
	printf("     ");
	for(idx=7;idx>=0;idx--) {
		ioxbit = (ioxdata >> idx) & (0x01);
		(ioxbit==1) ? printf("1 ") : printf("0 ");
	}
	printf("\n\r");
}

unsigned char readIox(void) {

	unsigned char read_byte = 0;

	start();
	if(write(IOXREAD) == ACK) {
		read_byte = read(NACK);
		stop();
	}
	return read_byte;
}

void int0_ISR(void) __interrupt(IE0_VECTOR) {

	g_ioxData=0;
	EA=0;

	//lcdputch('#');
	start();
	if(write(IOXREAD) == ACK) {
		g_ioxData = read(NACK);
		stop();
	}

	EA=1;
	f_intRecvd = TRUE;
}

/*=========================================FUNCTION MAIN==========================================*/

void main() {

	char cmd = 0;
	int i = 0;

	const unsigned char ioxMask0[IOXSIZE]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
	const unsigned char ioxMask1[IOXSIZE]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};

	unsigned char ioxState[IOXSIZE] = {0};

	char ipString[6] = {0};
	char ipStringLarge[100] = {0};
	unsigned char ipChar = 0;
	unsigned int ipInt = 0;
	unsigned char ret = 0;
	unsigned int ioxPin = 0;

	serialInit(0xFF); 						//0xFF gives Baud Rate 57600
	initAll();
	lcdinit();

	clearScr();
	P1_7 = ON;								//turn on LED
	clrLcd ();
	lcdputstr("LCD INITIALIZED ");
	printf("Initializing all IO Expander pins as Output (HIGH)\n\r");
    TCON = TCON | 0x01;						//Make INT0 Edge Triggered
	EA=1;
	if(writeIox(0xFF) == SUCCESS) {
		printf("Initializing all IO Expander pins as Output (HIGH)\n\r");
		for(i=0; i<IOXSIZE; i++) {
			ioxState[i] = IOXOUTPUT;
		}
	}
	else
		printf("Error: Unable to communicate with IOX\n\r");

	while (1) {
		printMenu();
		cmd = getchar();
		putchar(cmd);

		switch (cmd) {
			case '1': //set pin input
				ioxPin = getInput("\n\rPin # (0-7) > ", &ipChar, 1, TRUE, TRUE);

				if (ioxPin > 7) {
					menuInvalid();
					break;
				}

				ret = readIox();
				if((writeIox(ret | ioxMask0[ioxPin])) == SUCCESS) {
					printf("Port Pin %d Set\n\r", ioxPin);
				}

				ioxState[ioxPin] = IOXINPUT;
				ioxPrintIo(ioxState);

				getchar();
				break;

			case '2': //read pin using interrupt
				clearScr();
				putstr("Waiting for pin to change state \n\r");
				while(1) {
					IE |= 0x01 ;							//Enable EX0
					if(f_intRecvd == TRUE) {
						IE &= 0xFE ;						//Disable EX0
						f_intRecvd = FALSE;
						break;
					}
				}
				printf("Port Status: %0x%02X \n\r", g_ioxData);
				ioxPrintData(g_ioxData);

				getchar();
				break;

			case '3': //set pin as output and set initial value
				ioxPin = getInput("\n\rPin # (0-7) > ", &ipChar, 1, TRUE, TRUE);
				if (ioxPin > 7) {
					menuInvalid();
					break;
				}
				ipInt = getInput("\n\rInitial Value to write (0-1) > ", &ipChar, 1, TRUE, TRUE);
				if (ipInt > 1) {
					menuInvalid();
					break;
				}

				ioxState[ioxPin] = IOXOUTPUT;
				ioxPrintIo(ioxState);

				ret = readIox();
				if (ipInt == 1) {
					if((writeIox(ret | ioxMask0[ioxPin])) == SUCCESS) {
						printf("Port Pin %d Set\n\r", ioxPin);
					}
				}
				else {
					if((writeIox(ret & ioxMask1[ioxPin])) == SUCCESS) {
						printf("Port Pin %d Cleared\n\r", ioxPin);
					}
				}
				getchar();
				break;

			case '4': //change value of an output pin
				ioxPin = getInput("\n\rPin # (0-7) > ", &ipChar, 1, TRUE, TRUE);
				if (ioxPin > 7) {
					menuInvalid();
					break;
				}
				if (ioxState[ioxPin] != IOXOUTPUT) {
					putstr("Pin not set as output, will not write.\n\r");
					putstr("Press any key to go back\n\r");
					getchar();
					break;
				}
				ipInt = getInput("\n\rInitial Value to write (0-1) > ", &ipChar, 1, TRUE, TRUE);
				if (ipInt > 1) {
					menuInvalid();
					break;
				}
				//verified sanity, now go ahead with actually sending data
				ret = readIox();
				if (ipInt == 1) {
					if((writeIox(ret | ioxMask0[ioxPin])) == SUCCESS) {
						printf("Port Pin %d Set\n\r", ioxPin);
					}
				}
				else {
					if((writeIox(ret & ioxMask1[ioxPin])) == SUCCESS) {
						printf("Port Pin %d Cleared\n\r", ioxPin);
					}
				}
				getchar();

				break;

			case '5':
				clearScr();
				putstr("Port is: ");
				ioxPrintData(readIox());
				getchar();
				break;

			default:
				menuInvalid();
		}
	}
}

